Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for timeouts to improve hard realtime operation #618

Merged
merged 1 commit into from
Dec 21, 2021

Conversation

Uberi
Copy link
Contributor

@Uberi Uberi commented Nov 4, 2021

Most I2C master examples will hang the processor forever if an I2C slave device becomes unresponsive. As a result, the ArduinoCore-avr libraries recently added a number of new methods to the Wire library, to allow programs to recover from these conditions (e.g., by hard resetting the device).

This PR adds support for timeouts in the hardware-TWI and USI versions of ATTinyCore's Wire library.

  • Add timeout support in the TWI version of the Wire library.
  • Add timeout support in the USI-TWI version of the Wire library.
    • I used Greyson's work as a reference, so it looks quite similar. Essentially, anywhere that can block indefinitely, we implement a timeout to ensure an upper bound on time spent.

Testing

I only tested this on an ATTiny84A, so only the USI part of it got tested, the hardware-TWI part could really use some more. I used the test sketch from arduino/reference-en#895:

#include <Wire.h>

void setup() {
  Wire.begin(); // join i2c bus (address optional for master)
  #if defined(WIRE_HAS_TIMEOUT)
    Wire.setWireTimeout(3000 /* us */, true /* reset_on_timeout */);
  #endif
}

byte x = 0;

void loop() {
  /* First, send a command to the other device */
  Wire.beginTransmission(8); // transmit to device arduino/Arduino#8
  Wire.write(123);           // send command
  byte error = Wire.endTransmission(); // run transaction
  if (error) {
    Serial.println("Error occured when writing");
    if (error == 5)
      Serial.println("It was a timeout");
  }

  delay(100);

  /* Then, read the result */
  #if defined(WIRE_HAS_TIMEOUT)
  Wire.clearWireTimeoutFlag();
  #endif
  byte len = Wire.requestFrom(8, 1); // request 1 byte from device arduino/Arduino#8
  if (len == 0) {
    Serial.println("Error occured when reading");
    #if defined(WIRE_HAS_TIMEOUT)
    if (Wire.getWireTimeoutFlag())
      Serial.println("It was a timeout");
    #endif
  }



  delay(100);
}

To trigger a hang, I tied SCL low, preventing any device from raising the clock line - this represents a situation where the I2C device might be misbehaving. When I did, the timeout triggered, as expected, and the USI interface reset.

@SpenceKonde
Copy link
Owner

Huh, that's new. But thanks for implementing it.. what is the size cost to that feature over here?

Ughhhh @MX682X didn't we (mostly you) you just implement this in a way that doesn't match the api for dxcore didn't?

@MX682X
Copy link

MX682X commented Nov 8, 2021

...
...
...
this bug has existed for like over 10 years or so....
5 days ago they change the API... EDIT: ok, I've just noticed that it was in there since January. RIP
after I spent a bunch of time writing a small and timeout protected library that did not require micros()...
I feel betrayed.

After looking at the source, I think I know how to keep a small version and add the setTimeout function.
If millis is enabled in the context menu, setTimeout will work as expected, if not, a #warning will be thrown and it will use the CPU frequency as a constant to count against.

Thinking about it further, I don't see a need to change anything in the DxCore. I haven't changed anything in the API itself. The setTimout that people liked to use was from the print class. The timeout I've implemented has like 10-50ms until it fires (never measured). It will be enough for 98% of the users and is fairly small. Of course, when people start complaining about firing to slow or to fast, I would try to think about a solution with a user definable timeout. I'm kinda afraid to add a volatile 32bit variable that might end up using 200 bytes of flash in the two functions that need it.

@SpenceKonde
Copy link
Owner

Dear god. I had a look at merging this 2.0.0... that is going to be a BFD

It is not something I'm going to do without CI testing running on this repo so we can reports on the filesizes if nothing else. I am very concerned by the volume of code being added

Also it doesn't look like any attention has been paid to the fact that this core has an option to disable millis timekeeping.

Frankly, I don't think any timekeeping solution that relies on micros is appropriate in the Wire library, because that adds a requirement for not only micros being enabled (it often isn't, because people disable it to save flash) but also on interrupts being enabled....

The place in that code where it was doing the timeout in an ISR with a delay loop is IMO much more reasonable. You could get the delay loop by simply adding a counter to the polling loop. I'm fine with the timeout being an on/off thing with a fixed timeout like we did on megaTinyCore and DxCore and I'm not fine with pulling in the bloat of micros() just to use I2C if it's enabled at all on a core for parts with as little as 2k and 4k of flash.

@Uberi
Copy link
Contributor Author

Uberi commented Dec 21, 2021

Gotcha - haven't had time to iterate on this but @MX682X's solution here seems more suited to this use case.

Would you consider a counter-in-polling-loop version of this PR? It will be at least a few weeks until I get around to it, so I don't want to promise anything just yet or duplicate work if you're already on it!

My first thought is to use a 16-bit counter, so some napkin math says that at 8MHz and perhaps ~15 instructions per iteration, average 2 IPC, that's 246ms max.

@SpenceKonde
Copy link
Owner

Absolutely, I would love to see that as a PR

One other thing though --- start from the version of Wire in the 2.0.0-dev branch. It has been changed significatly, and when I was merging it, there were a bunch of places where I was confused about what was being changed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants